#include "common.h"
#include "sock_ssl.h"

static void init_prng();
static void sock_handle_ssl_write(int fd, short flag, void *arg);

void print_errors (void)
{
	unsigned long curerr = 0;
	while ((curerr = ERR_get_error ()) != 0)
		debug_log(0, "OpenSSL: %s\n", ERR_error_string (curerr, NULL));
}

static void init_prng ()
{
  char namebuf[256];
  const char *random_file;

  if (RAND_status ())
    /* The PRNG has been seeded; no further action is necessary. */
    return;

  /* Seed from a file specified by the user.  This will be the file
     specified with --random-file, $RANDFILE, if set, or ~/.rnd, if it
     exists.  */
  if (conf->ssl.random_file)
    random_file = conf->ssl.random_file;
  else
    {
      /* Get the random file name using RAND_file_name. */
      namebuf[0] = '\0';
      random_file = RAND_file_name (namebuf, sizeof (namebuf));
    }

  if (random_file && *random_file)
    /* Seed at most 16k (apparently arbitrary value borrowed from
       curl) from random file. */
    RAND_load_file (random_file, 16384);

  if (RAND_status ())
    return;

  /* Get random data from EGD if opt.egd_file was used.  */
  if (conf->ssl.egd_file && *conf->ssl.egd_file)
    RAND_egd (conf->ssl.egd_file);

  if (RAND_status ())
    return;

#ifdef WINDOWS
  /* Under Windows, we can try to seed the PRNG using screen content.
     This may or may not work, depending on whether we'll calling Wget
     interactively.  */

  RAND_screen ();
  if (RAND_status ())
    return;
#endif
}


SSL_CTX * sock_ssl_init()
{
  SSL_METHOD *meth;
  SSL_CTX *ssl_ctx = NULL;

  if (ssl_ctx)
    /* The SSL has already been initialized. */
    return ssl_ctx;

  /* Init the PRNG.  If that fails, bail out.  */
  init_prng ();
  if (RAND_status () != 1)
  {
      debug_log(1, "%s",
		 "Could not seed PRNG; consider using --random-file.\n");
      goto error;
  }

  SSL_library_init ();
  SSL_load_error_strings ();
  SSLeay_add_all_algorithms ();
  SSLeay_add_ssl_algorithms ();

  meth = SSLv3_client_method();

  ssl_ctx = SSL_CTX_new (meth);
  if (!ssl_ctx)
    goto error;

  SSL_CTX_set_default_verify_paths (ssl_ctx);
  SSL_CTX_load_verify_locations (ssl_ctx, conf->ssl.ca_cert, conf->ssl.ca_directory);

  /* SSL_VERIFY_NONE instructs OpenSSL not to abort SSL_connect if the
     certificate is invalid.  We verify the certificate separately in
     ssl_check_certificate, which provides much better diagnostics
     than examining the error stack after a failed SSL_connect.  */
  SSL_CTX_set_verify (ssl_ctx, SSL_VERIFY_NONE, NULL);

  if (conf->ssl.cert_file)
    if (SSL_CTX_use_certificate_file (ssl_ctx, conf->ssl.cert_file,
				      SSL_FILETYPE_PEM) != 1)
      goto error;
  if (conf->ssl.private_key)
    if (SSL_CTX_use_PrivateKey_file (ssl_ctx, conf->ssl.private_key,
				     SSL_FILETYPE_PEM) != 1)
      goto error;

  /* Since fd_write unconditionally assumes partial writes (and
     handles them correctly), allow them in OpenSSL.  */
  SSL_CTX_set_mode (ssl_ctx, SSL_MODE_ENABLE_PARTIAL_WRITE);

  return ssl_ctx;

 error:
  if (ssl_ctx)
    SSL_CTX_free (ssl_ctx);
  print_errors ();
  return NULL;;
}

int sock_ssl_connect(SSL *ssl, int fd)
{
  if (!ssl)
    goto error;
  if (!SSL_set_fd (ssl, fd))
    goto error;
  SSL_set_connect_state (ssl);
  if (SSL_connect (ssl) <= 0 || ssl->state != SSL_ST_OK)
    goto error;

  debug_log(5, "Handshake successful; connected socket %d to SSL\n",
	   fd);
  return 0;

 error:
  debug_log (1, "%s",  "SSL handshake failed.\n");
  print_errors ();
  if (ssl)
    SSL_free (ssl);
  return -1;
}

